package com.owc.common.oss;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.owc.common.configuration.OssConfig;
import com.owc.common.context.SpringContext;
import com.owc.common.enums.FileTypeEnum;
import com.owc.common.exception.BadInitException;
import com.owc.common.exception.NotFoundException;
import com.owc.common.exception.ParamDeficiencyException;
import com.owc.common.exception.UploadException;
import com.owc.common.tool.FileUtils;
import com.owc.common.tool.UtilsService;
import com.owc.domain.entity.OssResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @Author xiaom
 * @Date 2020/2/28 10:36
 * @Version 1.0.0
 * @Description <>
 **/
@Slf4j
public class OssUtils {

    //防止JVM指令重排 所以加上volatile修饰符
    private volatile static OssUtils _INSTALL_MODEL;

    /**
     * oss 工具客户端
     */
    private volatile OSSClient ossClient = null;
    /**
     * oss 配置文件
     */
    private OssConfig ossConfig;

    private OssUtils(){

    }

    //DCL 单例模式
    public synchronized static OssUtils getInstance(){
        if(_INSTALL_MODEL == null){
            synchronized (OssUtils.class){
                if(_INSTALL_MODEL == null){
                    _INSTALL_MODEL = init(null);
                }
            }
        }
        return _INSTALL_MODEL;
    }

    /**
     * Init oss client
     * @param config Base config.
     */
    public static OssUtils init(OssConfig config){
        if(config == null){
            config = SpringContext.getBean(OssConfig.class);
            if(config == null){
                throw new BadInitException("Init ossClient failed. Because oss config not found");
            }
        }
        _INSTALL_MODEL = new OssUtils();
        _INSTALL_MODEL.ossConfig = config;

        if(_INSTALL_MODEL.ossClient == null){
            synchronized (OssUtils.class){
                if(_INSTALL_MODEL.ossClient == null){
                    _INSTALL_MODEL.ossClient = new OSSClient(config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
                    initComponent();
                }
            }
        }
        log.info("OssClient initialized successfully.. ");
        return _INSTALL_MODEL;
    }

    /**
     * initialization config.
     */
    private static void initComponent(){
        initBucket();
        initFolder();
    }

    private static void initFolder() {
        String folders = _INSTALL_MODEL.ossConfig.getFolders();
        if(StringUtils.isBlank(folders)){
            return;
        }
        String[] folderArray = folders.split(",");
        for (String folder : folderArray) {
            _INSTALL_MODEL.createFolder(folder + "/");
        }
    }

    private static void initBucket(){
        String bucketName = _INSTALL_MODEL.ossConfig.getBucketName();
        if(_INSTALL_MODEL.doesBucketExist(bucketName)){
            log.warn("Bucket:[{}] already exist.",bucketName);
            return;
        }
        _INSTALL_MODEL.createBucket(bucketName);
    }

    /**
     * Upload file to 'file' in Folder
     * @param filePath        file path
     * @param useOriginalName Whether to use default file name. False(do not use,Use GUID name). True(Use original file name)
     */
    public OssResult upload(String filePath,boolean useOriginalName) throws FileUploadException {
        return this.upload(null,filePath,OssConst.FILE_PREFIX,useOriginalName);
    }

    public OssResult uploadToImage(String filePath,boolean useOriginalName) throws FileUploadException {
        return this.upload(null,filePath,OssConst.IMAGE_PREFIX,useOriginalName);
    }

    public OssResult uploadToRelease(String filePath,boolean useOriginalName) throws FileUploadException {
        return this.upload(null,filePath,OssConst.RELEASE_PREFIX,useOriginalName);
    }

    public OssResult upload(MultipartFile file, boolean useOriginalName, FileTypeEnum fileTypeEnum) throws IOException, FileUploadException {
        String objectName;
        if(useOriginalName){
            objectName = file.getOriginalFilename();
        }else{
            //Get file suffix name.
            String suffix = FileUtils.getFileExt(file.getOriginalFilename());
            objectName = UtilsService.getGuid() + "." + suffix;
        }
        if(fileTypeEnum == null){
            fileTypeEnum = FileTypeEnum.FILE;
        }
        objectName = fileTypeEnum.getFolder() + objectName;

        PutObjectResult objectResult = this.upload(ossConfig.getBucketName(),objectName, file.getInputStream());
        return new OssResult(ossConfig.getUrl() +  objectName,objectResult.getETag());
    }

    /**
     * upload by file path.
     * Use default bucket.
     * @param objectName  file name
     * @param filePath    file path
     * @param folder     目录
     * @param originalName     是否使用原始文件名称
     */
    private OssResult upload(String objectName, String filePath, String folder, boolean originalName) throws FileUploadException {
        //Use default bucket.
        String bucketName = ossConfig.getBucketName();

        //valid file path
        if(StringUtils.isBlank(filePath)){
            throw new ParamDeficiencyException("Upload file to oss. File path must not be null.");
        }
        if(!FileUtils.isFileExist(filePath)){
            throw new NotFoundException("Upload file to oss. File does not exist");
        }
        //If objectName is null, Use default file name.
        if(objectName == null){
            if(originalName){
                objectName = FileUtils.getFileName(filePath);
            }else{
                //Get file suffix name.
                String suffix = FileUtils.getFileExt(filePath);
                objectName = UtilsService.getGuid() + "." + suffix;
            }
        }

        byte[] bytes = FileUtils.transToByte(filePath);
        if(bytes == null){
            throw new UploadException("Upload file failed. file trans from byte error");
        }

        //Assembly url.
        if(folder != null){
            objectName = folder + objectName;
        }

        PutObjectResult result = upload(bucketName,objectName,new ByteArrayInputStream(bytes));
        if(result == null){
            return null;
        }
        log.info("Upload file successful. Oss eTag:[{}]",result.getETag());
        return new OssResult(ossConfig.getUrl() +  objectName,result.getETag());
    }

    /**
     * Upload file to oss server.
     */
    private PutObjectResult upload(String bucketName, String objectName, InputStream stream) throws FileUploadException {
        try {
            String fileType = getcontentType(objectName.substring(objectName.lastIndexOf(".")));
            ObjectMetadata metadata = new ObjectMetadata();
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(stream.available());
            objectMetadata.setCacheControl("no-cache");
            objectMetadata.setHeader("Pragma", "no-cache");
            objectMetadata.setContentType(fileType);
            objectMetadata.setContentDisposition("inline;filename=" + objectName);
            return this.ossClient.putObject(bucketName, objectName, stream,metadata);
        }catch (Exception e){
            log.error("Upload file throw error. exMsg:{}",e.getMessage());
            throw new FileUploadException(e.getMessage());
        }finally {
            try {
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Judge bucket exist.
     * @param bucketName name
     * @return boolean
     */
    private boolean doesBucketExist(String bucketName){
        if(StringUtils.isBlank(bucketName)){
            throw new ParamDeficiencyException("BucketName must not be null.");
        }
        return ossClient.doesBucketExist(bucketName);
    }

    /**
     * Create new bucket
     * @param bucketName name
     */
    private Boolean createBucket(String bucketName){
        if(doesBucketExist(bucketName)){
            log.warn("Bucket:[{}] already exist.",bucketName);
            return false;
        }
        try {
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            // 设置存储空间的权限为公共读，默认是私有。
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            // 设置存储空间的存储类型为低频访问类型，默认是标准类型。
            createBucketRequest.setStorageClass(StorageClass.IA);
            this.ossClient.createBucket(createBucketRequest);
        }catch (Exception e){
            log.error("Create bucket failed. exMsg:{}",e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * Create folder.
     * @param folder folder name
     */
    private String createFolder(String folder){
        String bucketName = ossConfig.getBucketName();
        if (!ossClient.doesObjectExist(bucketName, folder)) {
            // 创建文件夹
            ossClient.putObject(bucketName, folder, new ByteArrayInputStream(new byte[0]));
            // 得到文件夹名
            OSSObject object = ossClient.getObject(bucketName, folder);
            String fileDir = object.getKey();
            log.info("Create folder successful. Folder name:[{}]",fileDir);
            return fileDir;
        }
        return folder;
    }


    /**
     * Description: 判断OSS服务文件上传时文件的contentType
     *
     * @param FilenameExtension 文件后缀
     * @return String
     */
    private static String getcontentType(String FilenameExtension) {
        if (FilenameExtension.equalsIgnoreCase(".bmp")) {
            return "image/bmp";
        }
        if (FilenameExtension.equalsIgnoreCase(".gif")) {
            return "image/gif";
        }
        if (FilenameExtension.equalsIgnoreCase(".jpeg") ||
                FilenameExtension.equalsIgnoreCase(".jpg") ||
                FilenameExtension.equalsIgnoreCase(".png")) {
            return "image/jpeg";
        }
        if (FilenameExtension.equalsIgnoreCase(".html")) {
            return "text/html";
        }
        if (FilenameExtension.equalsIgnoreCase(".txt")) {
            return "text/plain";
        }
        if (FilenameExtension.equalsIgnoreCase(".vsd")) {
            return "application/vnd.visio";
        }
        if (FilenameExtension.equalsIgnoreCase(".pptx") ||
                FilenameExtension.equalsIgnoreCase(".ppt")) {
            return "application/vnd.ms-powerpoint";
        }
        if (FilenameExtension.equalsIgnoreCase(".docx") ||
                FilenameExtension.equalsIgnoreCase(".doc")) {
            return "application/msword";
        }
        if (FilenameExtension.equalsIgnoreCase(".xml")) {
            return "text/xml";
        }
        return "image/jpeg";
    }
}
